package recommend

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"mall/common/cachex"
	"mall/common/globalkey"
	"mall/common/jwt"
	"mall/common/resultx"
	"mall/common/utils/pointy"
	"mall/service/blog/rpc/blogclient"
	"mall/service/forum/api/internal/lib/forumreaction"
	"mall/service/forum/api/internal/lib/userrank"
	"mall/service/forum/api/internal/logic/util"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"
	"mall/service/forum/rpc/forumclient"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type GetItemLatestLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// 查询item相似度
func NewGetItemLatestLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetItemLatestLogic {
	return &GetItemLatestLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *GetItemLatestLogic) GetItemLatest(req *types.ItemSearchReq) (*types.ItemSearchResp, error) {
	userId := jwt.GetUserId(l.ctx)
	if userId == 0 {
		return nil, resultx.NewErrCode(resultx.TOKEN_EXPIRE_ERROR)
	}

	var postIds []string
	//先查缓存，缓存有就不查推荐系统
	key := globalkey.ItemLatestCache + strconv.FormatInt(int64(userId), 10)
	cacheRes, err := l.svcCtx.BizRedis.GetCtx(l.ctx, key)
	if err != nil || len(strings.TrimSpace(cacheRes)) == 0 {
		//无缓存 查询更新
		res, err := l.svcCtx.GorseClient.GetItemLatest(
			l.svcCtx.Config.Query.GorseUrl,
			strconv.FormatUint(userId, 10),
			strconv.FormatUint(uint64(req.Offset), 10),
			strconv.FormatUint(uint64(req.PageSize), 10),
		)
		if err != nil {
			logx.Errorf("GetItemLatest GorseClient.GetItemLatest Error: %+v", err)
			return nil, err
		}

		if res != nil && len(res) > 0 {
			if list, ok := res["list"]; ok && list != nil {
				for _, v := range list.([]any) {
					if id, iOk := v.(map[string]any)["id"]; iOk && id != nil {
						postIds = append(postIds, id.(string))
					}
				}
			}
		}
		//更新缓存
		if len(postIds) > 0 {
			err = l.svcCtx.BizRedis.SetCtx(l.ctx, key, strings.Join(postIds, ","))
			if err != nil {
				logx.Errorf("GetItemLatest SetCtx Error: %+v", err)
				return nil, err
			}
		}
	} else {
		//有缓存
		postIds = strings.Split(cacheRes, ",")
	}

	resp := &types.ItemSearchResp{}
	if len(postIds) > 0 {
		//区分 blogPost: knowledgeBase:
		var knowledgeBaseIds []string
		var blogPostIds []string
		for _, v := range postIds {
			if strings.Contains(v, globalkey.GorseKnowledgeBasePrefix) {
				split := strings.Split(v, ":")
				if len(split) == 2 {
					if len(strings.TrimSpace(split[1])) > 0 {
						knowledgeBaseIds = append(knowledgeBaseIds, split[1])
					}
				}
			}

			if strings.Contains(v, globalkey.GorseBLogPostPrefix) {
				split := strings.Split(v, ":")
				if len(split) == 2 {
					if len(strings.TrimSpace(split[1])) > 0 {
						blogPostIds = append(blogPostIds, split[1])
					}
				}
			}
		}

		if len(knowledgeBaseIds) > 0 {
			entry, err := l.getEntryListByIds(strings.Join(knowledgeBaseIds, ","))
			if err != nil {
				return nil, err
			}
			resp.KbEntry = entry
		}

		if len(blogPostIds) > 0 {
			forumPostList, err := l.getForumPost(blogPostIds, userId)
			if err != nil {
				return nil, err
			}
			resp.ForumPost = forumPostList
		}
	}
	return resp, nil
}

func (l *GetItemLatestLogic) getForumPost(ids []string, userId uint64) ([]*types.PostInfo, error) {
	var filters []string //条件过滤
	filters = append(filters, "Id,in:"+strings.Join(ids, ","))
	filters = append(filters, "Status,equalTo:"+strconv.FormatInt(int64(globalkey.StatusEnabled), 10))
	filters = append(filters, "AuthorId,notEqualTo:"+strconv.FormatInt(int64(userId), 10))
	var platformId = globalkey.ThirdPartyPlatformWechatMini
	//获取香评列表
	res, err := l.svcCtx.ForumRpc.GetPostList(l.ctx, &forumclient.BaseListReq{
		// Preload:    pointy.GetPointer(preload),
		// EagerLoad:  pointy.GetPointer(eagerLoad),
		Filter:     pointy.GetPointer(strings.Join(filters, ";")),
		VisitorId:  pointy.GetPointer(userId),
		PlatformId: pointy.GetPointer(platformId),
	})
	if err != nil {
		return nil, err
	}
	var forumPostList []*types.PostInfo
	if len(res.List) > 0 {
		var perfumeMap map[uint64]*types.KbEntryInfo
		var ids []string
		for _, v := range res.List {
			if v.ParentId != nil && *v.ParentId > 0 {
				ids = append(ids, strconv.FormatInt(int64(*v.ParentId), 10))
			}
		}

		if len(ids) == 0 {
			return nil, nil
		}

		perfumeMap, err = l.getPerfumeMap(ids)
		if err != nil {
			return nil, err
		}

		if perfumeMap == nil {
			return nil, nil
		}

		var userRankMap map[uint32]*types.UserRankInfo
		userRankMap = l.getUserRankMap(userId)

		var postIds []string
		for _, v := range res.List {
			if v.Id != nil && *v.Id > 0 {
				postIds = append(postIds, strconv.FormatInt(int64(*v.Id), 10))
			}
		}
		likeMap := l.getPostLikeBatch(postIds, strconv.FormatInt(int64(userId), 10))

		for _, v := range res.List {
			var parent *types.KbEntryInfo
			if v.ParentId != nil && *v.ParentId > 0 {
				if pv, ok := perfumeMap[*v.ParentId]; ok {
					parent = pv
				}
			}

			var photos []*types.MediaInfo
			if len(v.Media) > 0 {
				for _, m := range v.Media {
					var url string
					if m.Url != nil && len(*m.Url) > 0 {
						url = *m.Url
					}
					if len(url) == 0 && m.Uri != nil && len(*m.Uri) > 0 {
						url = *m.Uri
					}
					var thumbnail *bool
					if m.Thumbnail != nil {
						thumbnail = pointy.GetPointer(*m.Thumbnail > 0)
					}
					photos = append(photos, &types.MediaInfo{
						Uid:       m.Uid,
						Id:        m.Id,
						Url:       pointy.GetPointer(url),
						Thumbnail: thumbnail,
						AltText:   m.AltText,
						Details:   m.MetaData,
					})
				}
			}

			var isLiked *bool
			var likeCount *uint64
			if likeMap != nil && v.Id != nil && *v.Id > 0 {
				if likeStatus, ok := likeMap[int64(*v.Id)]; ok {
					isLiked = pointy.GetPointer(likeStatus.Status > 0)
					likeCount = pointy.GetPointer(uint64(likeStatus.Count))
				}
			}
			var author *types.AuthorInfo
			var levelName *string
			var levelUrl *string
			if userRankMap != nil && v.AuthorRankId != nil && *v.AuthorRankId > 0 {
				if userRank, ok := userRankMap[*v.AuthorRankId]; ok {
					levelName = userRank.Name
					levelUrl = userRank.MediaUrl
				}
			}
			if v.AuthorNickname != nil {
				author = &types.AuthorInfo{
					Id:        v.AuthorId,
					Name:      v.AuthorNickname,
					Avatar:    v.AuthorAvatar,
					Ip:        v.AuthorIP,
					Level:     v.AuthorRankId,
					LevelName: levelName,
					LevelUrl:  levelUrl,
				}
			}

			var commentStatus *bool
			if v.CommentStatus != nil {
				commentStatus = pointy.GetPointer(*v.CommentStatus > 0)
			}
			var createdAt *string
			if v.CreatedAt != nil && len(*v.CreatedAt) > 0 {
				createdAt = pointy.GetPointer(util.GetTimeFromNow(*v.CreatedAt))
			}

			forumPostList = append(forumPostList, &types.PostInfo{
				Id:            v.Id,
				ContentHtml:   v.PostContent,
				AuthorId:      v.AuthorId,
				ParentId:      v.ParentId,
				Parent:        parent,
				Photos:        photos,
				Status:        v.Status,
				IsLiked:       isLiked,
				LikeCount:     likeCount,
				CommentStatus: commentStatus,
				Author:        author,
				CreatedAt:     createdAt,
				UpdatedAt:     v.CreatedAt,
			})
		}
	}
	return forumPostList, nil
}

func (l *GetItemLatestLogic) getPerfumeMap(ids []string) (map[uint64]*types.KbEntryInfo, error) {
	if len(ids) == 0 {
		return nil, nil
	}

	var preload string = "blogBook:Id,Title,Slug;blogMedia:Id,Uid,Url,Thumbnail;blogBookRelation:brand"
	var eagerLoad string = "blogBook:blogMedia,blogBookRelation"
	res, err := l.svcCtx.BlogRpc.BookList(l.ctx, &blogclient.BookListReq{
		Preload:   preload,
		EagerLoad: eagerLoad,
		EntryIds:  strings.Join(ids, ","),
	})
	if err != nil {
		logrus.Info(fmt.Sprintf("GetPostList getPerfumeMap err: %+v", err))
		return nil, err
	}

	if len(res.Data) > 0 {
		resp := make(map[uint64]*types.KbEntryInfo)
		for _, item := range res.Data {
			var media []*types.TmpMediaInfo
			if len(item.Media) > 0 {
				for _, v := range item.Media {
					media = append(media, &types.TmpMediaInfo{
						Uid:       pointy.GetPointer(uint64(v.Uid)),
						Id:        pointy.GetPointer(strconv.FormatInt(int64(v.Id), 10)),
						Url:       pointy.GetPointer(v.Url),
						Thumbnail: pointy.GetPointer(uint32(v.Thumbnail)),
						AltText:   pointy.GetPointer(v.AltText),
						MetaData:  pointy.GetPointer(v.Meta),
					})
				}
			}
			var brand []*types.KbEntryInfo
			if len(item.Relations) > 0 {
				for _, v := range item.Relations {
					if v.Section == "brand" {
						brand = append(brand, &types.KbEntryInfo{
							Id:    pointy.GetPointer(uint64(v.Id)),
							Title: pointy.GetPointer(v.Title),
							Slug:  pointy.GetPointer(v.Slug),
						})
					}
				}
			}

			resp[uint64(item.Id)] = &types.KbEntryInfo{
				Id:     pointy.GetPointer(uint64(item.Id)),
				Title:  pointy.GetPointer(item.Title),
				Slug:   pointy.GetPointer(item.Slug),
				Media:  media,
				Brands: brand,
			}
		}

		return resp, nil
	}

	return nil, nil
}

func (l *GetItemLatestLogic) getPostLikeBatch(postIds []string, userId string) map[int64]*forumreaction.ForumReactionStatus {
	forumReaction := forumreaction.NewForumReaction(l.ctx, l.svcCtx, cachex.ForumReviewLike)
	likeMap, err := forumReaction.GetReactionMap(postIds, userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("GetPostList getPostLikeBatch GetReactionMap err: %+v ", err))
		return nil
	}

	// logrus.Info(fmt.Sprintf("GetPostList getPostLikeBatch GetReactionMap : %+v ", likeMap))
	var objectIds []string
	var extraIds []string
	for i, v := range likeMap {
		// logrus.Info(fmt.Sprintf("GetPostList getPostLikeBatch GetReactionMap item %d : %+v ", i, v))
		if v.Status == -1 {
			objectIds = append(objectIds, strconv.FormatInt(i, 10))
		}
		if v.Count == -1 {
			extraIds = append(extraIds, strconv.FormatInt(i, 10))
		}
	}
	if len(objectIds) > 0 || len(extraIds) > 0 {
		logMap, extraMap, err := l.getPostLikeMap(objectIds, userId, extraIds)
		if err != nil {
			logrus.Info(fmt.Sprintf("GetPostList getPostLikeBatch getPostLikeMap err: %+v ", err))
			return nil
		}

		for i, v := range likeMap {
			if v.Status == -1 {
				if status, ok := logMap[i]; ok { // 数据库有记录
					v.Status = status
					forumReaction.InitStatus(
						strconv.FormatInt(i, 10),
						userId,
						strconv.FormatInt(int64(status), 10),
					)
				} else {
					v.Status = 0
					forumReaction.InitStatus(strconv.FormatInt(i, 10), userId, "-1")
				}
			}
			if v.Count == -1 {
				if count, ok := extraMap[i]; ok { // 数据库有记录
					v.Count = count
					forumReaction.InitCount(strconv.FormatInt(i, 10), strconv.FormatInt(count, 10))
				} else {
					v.Count = 0
					forumReaction.InitCount(strconv.FormatInt(i, 10), "0")
				}
			}
			likeMap[i] = v
		}
	}

	return likeMap
}

func (l *GetItemLatestLogic) getPostLikeMap(
	postIds []string,
	userId string,
	extraIds []string,
) (resp map[int64]int32, extra map[int64]int64, err error) {
	resp = make(map[int64]int32)
	extra = make(map[int64]int64)

	if len(postIds) == 0 && len(extraIds) == 0 {
		return
	}

	var filters []string
	if len(postIds) > 0 {
		filters = append(filters, "PostId,in:"+strings.Join(postIds, ","))
		filters = append(filters, "UserId,equalTo:"+userId)
		filters = append(filters, "Type,equalTo:"+cachex.ForumReviewLike)
	}
	var preFilters []string
	if len(extraIds) > 0 {
		preFilters = append(preFilters, "PostId,in:"+strings.Join(extraIds, ","))
		preFilters = append(preFilters, "Type,equalTo:"+cachex.ForumReviewLike)
	}
	res, err := l.svcCtx.ForumRpc.GetReactionLogList(l.ctx, &forumclient.BaseListReq{
		Preload:         pointy.GetPointer("forumReactionLog:PostId,UserId,Status,Count"),
		Filter:          pointy.GetPointer(strings.Join(filters, ";")),
		PreFilterEntity: pointy.GetPointer(cachex.ForumReviewLike),
		PreFilter:       pointy.GetPointer(strings.Join(preFilters, ";")),
	})
	if err != nil {
		return nil, nil, err
	}

	if len(res.List) > 0 {
		for _, v := range res.List {
			if v.PostId == nil || v.Status == nil || *v.PostId == 0 || *v.Status == 0 {
				resp[int64(*v.PostId)] = int32(*v.Status)
			}
		}
	}
	if len(res.Extra) > 0 {
		for _, v := range res.Extra {
			if v.PostId == nil || v.Count == nil || *v.PostId == 0 || *v.Count == 0 {
				extra[int64(*v.PostId)] = int64(*v.Count)
			}
		}
	}

	return
}

func (l *GetItemLatestLogic) getUserRankMap(userId uint64) map[uint32]*types.UserRankInfo {
	userRank := userrank.NewUserRank(l.ctx, l.svcCtx)
	userRankList, err := userRank.List(userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("GetPostList getUserRankMap err: %+v", err))
		return nil
	}

	if len(userRankList) > 0 {
		resp := make(map[uint32]*types.UserRankInfo)
		for _, v := range userRankList {
			if v.Id != nil && *v.Id > 0 {
				resp[uint32(*v.Id)] = v
			}
		}

		return resp
	}

	return nil
}

func (l *GetItemLatestLogic) getEntryListByIds(ids string) (resp []*types.KbEntryInfo, err error) {
	preload := "blogBook:Id,Title,Slug"
	eagerLoad := "blogBook:blogBookMeta"
	preload += ";blogBookMeta:mediaId,mediaUrl,reviewCount"

	var filters []string
	filters = append(filters, "Status,equalTo:"+strconv.FormatInt(int64(globalkey.PostStatusPublish), 10))
	filters = append(filters, "Id,in:"+ids)

	res, err := l.svcCtx.BlogRpc.GetKBPostListTmp(l.ctx, &blogclient.BaseListReq{
		Preload:   pointy.GetPointer(preload),
		EagerLoad: pointy.GetPointer(eagerLoad),
		Filter:    pointy.GetPointer(strings.Join(filters, ";")),
	})
	if err != nil {
		return nil, err
	}

	if len(res.List) > 0 {
		for _, v := range res.List {
			var mediaUrl *string
			var reviewCount *uint32
			if len(v.Meta) > 0 {
				for _, m := range v.Meta {
					if m.Key == nil || m.Value == nil {
						continue
					}

					val := *m.Value
					switch *m.Key {
					case "mediaUrl":
						mediaUrl = pointy.GetPointer(val)
					case "reviewCount":
						if i, err := strconv.ParseInt(val, 10, 32); err == nil {
							reviewCount = pointy.GetPointer(uint32(i))
						}
					}
				}
			}

			resp = append(resp, &types.KbEntryInfo{
				Id:          v.Id,
				Title:       v.Title,
				Slug:        v.Slug,
				ImageUrl:    mediaUrl,
				ReviewCount: reviewCount,
			})
		}
	}

	return resp, nil
}
